{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "d068971c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import random \n",
    "import torch\n",
    "import torch.nn.functional as F\n",
    "from torch.utils.data import Dataset, DataLoader \n",
    "import os\n",
    " \n",
    "seed = 114514\n",
    "np.random.seed(seed)\n",
    "random.seed(seed)\n",
    "BATCH_SIZE = 512 \n",
    "hidden_dim = 16\n",
    "epochs = 10\n",
    "device = torch.device('cuda:0') if torch.cuda.is_available() else torch.device('cpu')\n",
    "print(device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "64ba0aa7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "共53424个用户，10000本图书，5869631条记录\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user_id</th>\n",
       "      <th>item_id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>257</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0</td>\n",
       "      <td>267</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0</td>\n",
       "      <td>5555</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0</td>\n",
       "      <td>3637</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0</td>\n",
       "      <td>1795</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   user_id  item_id\n",
       "0        0      257\n",
       "1        0      267\n",
       "2        0     5555\n",
       "3        0     3637\n",
       "4        0     1795"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.read_csv('./datasets/train_dataset.csv')\n",
    "print('共{}个用户，{}本图书，{}条记录'.format(max(df['user_id'])+1, max(df['item_id'])+1, len(df)))\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "9aeef594",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tqdm\n",
    "class Goodbooks(Dataset):\n",
    "    def __init__(self, df, mode='training', negs = 99):\n",
    "        super().__init__() \n",
    "        self.df = df\n",
    "        self.mode = mode \n",
    "        self.book_nums = max(df['item_id'])+1\n",
    "        self.user_nums = max(df['user_id'])+1 \n",
    "        self._init_dataset()        \n",
    "        \n",
    "    def _init_dataset(self):\n",
    "        self.Xs = [] \n",
    "        self.user_book_map = {}\n",
    "        for i in range(self.user_nums):\n",
    "            self.user_book_map[i] = [] \n",
    "            \n",
    "        for index, row in self.df.iterrows():\n",
    "            user_id, book_id = row\n",
    "            self.user_book_map[user_id].append(book_id) \n",
    "            \n",
    "        if self.mode == 'training':\n",
    "            for user, items in tqdm.tqdm(self.user_book_map.items()):\n",
    "                for item in items[:-1]:\n",
    "                    self.Xs.append((user, item, 1))\n",
    "                    for _ in range(3):\n",
    "                        while True:\n",
    "                            neg_sample = random.randint(0, self.book_nums-1)\n",
    "                            if neg_sample not in self.user_book_map[user]:\n",
    "                                self.Xs.append((user, neg_sample, 0))\n",
    "                                break\n",
    "                            \n",
    "        elif self.mode == 'validation':\n",
    "            for user, items in tqdm.tqdm(self.user_book_map.items()):\n",
    "                if len(items) == 0:\n",
    "                    continue\n",
    "                self.Xs.append((user, items[-1]))\n",
    "    \n",
    "    def __getitem__(self, index): \n",
    "        if self.mode == 'training':\n",
    "            user_id, book_id, label = self.Xs[index]\n",
    "            return user_id, book_id, label\n",
    "        elif self.mode == 'validation':\n",
    "            user_id, book_id = self.Xs[index] \n",
    "            negs = list(random.sample(\n",
    "                list(set(range(self.book_nums)) - set(self.user_book_map[user_id])),\n",
    "                k=99\n",
    "            ))\n",
    "            return user_id, book_id, torch.LongTensor(negs)\n",
    "    \n",
    "    def __len__(self):\n",
    "        return len(self.Xs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "1e7a10e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|███████████████████████████████████████████████████████████████████████████| 53424/53424 [01:21<00:00, 659.28it/s]\n",
      "100%|█████████████████████████████████████████████████████████████████████████| 53424/53424 [00:01<00:00, 35313.01it/s]\n"
     ]
    }
   ],
   "source": [
    "#建立训练和验证dataloader\n",
    "traindataset = Goodbooks(df, 'training')\n",
    "validdataset = Goodbooks(df, 'validation')\n",
    " \n",
    "trainloader = DataLoader(traindataset, batch_size=BATCH_SIZE, shuffle=True, drop_last=False, num_workers=0)\n",
    "validloader = DataLoader(validdataset, batch_size=BATCH_SIZE, shuffle=True, drop_last=False, num_workers=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "902d0f20",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 构建模型\n",
    "class NCFModel(torch.nn.Module):\n",
    "    def __init__(self, hidden_dim, user_num, item_num, mlp_layer_num=4, weight_decay = 1e-5, dropout=0.5):\n",
    "        super().__init__()\n",
    "        self.hidden_dim = hidden_dim\n",
    "        self.user_num = user_num\n",
    "        self.item_num = item_num\n",
    "        self.mlp_layer_num = mlp_layer_num\n",
    "        self.weight_decay = weight_decay\n",
    "        self.dropout=dropout        \n",
    "        self.mlp_user_embedding = torch.nn.Embedding(user_num, hidden_dim * (2 ** (self.mlp_layer_num - 1)))\n",
    "        self.mlp_item_embedding = torch.nn.Embedding(item_num, hidden_dim * (2 ** (self.mlp_layer_num - 1))) \n",
    "        self.gmf_user_embedding = torch.nn.Embedding(user_num, hidden_dim)\n",
    "        self.gmf_item_embedding = torch.nn.Embedding(item_num, hidden_dim) \n",
    "        mlp_Layers = []\n",
    "        input_size = int(hidden_dim*(2 ** (self.mlp_layer_num)))\n",
    "        for i in range(self.mlp_layer_num):\n",
    "            mlp_Layers.append(torch.nn.Linear(int(input_size), int(input_size / 2)))\n",
    "            mlp_Layers.append(torch.nn.Dropout(self.dropout))\n",
    "            mlp_Layers.append(torch.nn.ReLU())\n",
    "            input_size /= 2\n",
    "        self.mlp_layers = torch.nn.Sequential(*mlp_Layers) \n",
    "        self.output_layer = torch.nn.Linear(2*self.hidden_dim, 1)\n",
    "    \n",
    "    def forward(self, user, item):\n",
    "        user_gmf_embedding = self.gmf_user_embedding(user)\n",
    "        item_gmf_embedding = self.gmf_item_embedding(item) \n",
    "        user_mlp_embedding = self.mlp_user_embedding(user)\n",
    "        item_mlp_embedding = self.mlp_item_embedding(item) \n",
    "        gmf_output = user_gmf_embedding * item_gmf_embedding \n",
    "        mlp_input = torch.cat([user_mlp_embedding, item_mlp_embedding], dim=-1)\n",
    "        mlp_output = self.mlp_layers(mlp_input) \n",
    "        output = torch.sigmoid(self.output_layer(torch.cat([gmf_output, mlp_output], dim=-1))).squeeze(-1)\n",
    "        return output\n",
    "    \n",
    "    def predict(self, user, item):\n",
    "        self.eval()\n",
    "        with torch.no_grad():\n",
    "            user_gmf_embedding = self.gmf_user_embedding(user)\n",
    "            item_gmf_embedding = self.gmf_item_embedding(item) \n",
    "            user_mlp_embedding = self.mlp_user_embedding(user)\n",
    "            item_mlp_embedding = self.mlp_item_embedding(item) \n",
    "            gmf_output = user_gmf_embedding.unsqueeze(1) * item_gmf_embedding \n",
    "            user_mlp_embedding = user_mlp_embedding.unsqueeze(1).expand(-1, item_mlp_embedding.shape[1], -1)\n",
    "            mlp_input = torch.cat([user_mlp_embedding, item_mlp_embedding], dim=-1)\n",
    "            mlp_output = self.mlp_layers(mlp_input) \n",
    "        output = torch.sigmoid(self.output_layer(torch.cat([gmf_output, mlp_output], dim=-1))).squeeze(-1)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "223c3797",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0 finished, average loss 0.4501930531704615, hits@20 0.3932876609763402\n",
      "Epoch 1 finished, average loss 0.37845535087792703, hits@20 0.48399595687331537\n",
      "Epoch 2 finished, average loss 0.3319641511680716, hits@20 0.5676849356094639\n",
      "Epoch 3 finished, average loss 0.29305555291578805, hits@20 0.6054956573824498\n",
      "Epoch 4 finished, average loss 0.2677067052805796, hits@20 0.6342093441150045\n",
      "Epoch 5 finished, average loss 0.24633210104870493, hits@20 0.6494646600778676\n",
      "Epoch 6 finished, average loss 0.2291174693052775, hits@20 0.6633722671458521\n",
      "Epoch 7 finished, average loss 0.21472287009483282, hits@20 0.6762129380053908\n",
      "Epoch 8 finished, average loss 0.20242702511145771, hits@20 0.6833071278825996\n",
      "Epoch 9 finished, average loss 0.1919066570053192, hits@20 0.6916179994010183\n"
     ]
    }
   ],
   "source": [
    "model = NCFModel(hidden_dim, traindataset.user_nums, traindataset.book_nums).to(device)\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n",
    "crit = torch.nn.BCELoss() \n",
    "loss_for_plot = []\n",
    "hits_for_plot = []    \n",
    "for epoch in range(epochs): \n",
    "    losses = []\n",
    "    for index, data in enumerate(trainloader):\n",
    "        user, item, label = data\n",
    "        user, item, label = user.to(device), item.to(device), label.to(device).float()\n",
    "        y_ = model(user, item).squeeze() \n",
    "        loss = crit(y_, label)\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step() \n",
    "        losses.append(loss.detach().cpu().item())            \n",
    "    hits = []    \n",
    "    for index, data in enumerate(validloader):\n",
    "        user, pos, neg = data\n",
    "        pos = pos.unsqueeze(1)\n",
    "        all_data = torch.cat([pos, neg], dim=-1)\n",
    "        output = model.predict(user.to(device), all_data.to(device)).detach().cpu()        \n",
    "        for batch in output:\n",
    "            if 0 not in (-batch).argsort()[:10]:\n",
    "                hits.append(0)\n",
    "            else:\n",
    "                hits.append(1)\n",
    "    print('Epoch {} finished, average loss {}, hits@20 {}'.format(epoch, sum(losses)/len(losses), sum(hits)/len(hits)))\n",
    "    loss_for_plot.append(sum(losses)/len(losses))\n",
    "    hits_for_plot.append(sum(hits)/len(hits))\n",
    "# 模型保存\n",
    "torch.save(model.state_dict(), './model.h5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "181c0bf6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAY0AAAEGCAYAAACZ0MnKAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAtSklEQVR4nO3deXxU9fX/8ddhE1kUFaQVVEBBBRXEoHVDMNa6VNGKCm5VVEhFf9Vv60JpbetW0Wq1VYtUrK3WuoOoiNqAEKsiARFZRBE3pCpu4IYInN8fn0k7xkDuJHPnzvJ+Ph55ZObO/SSHYSZn7mc5H3N3REREomiSdAAiIlI4lDRERCQyJQ0REYlMSUNERCJT0hARkciaJR1ANrVv3967dOmSdBhSYGbPnv2Bu3dIOo7G0utfGiLT139RJY0uXbpQXV2ddBhSYMzszaRjyAa9/qUhMn39q3tKJCZmdqiZLTazJWZ2cR2PX2Bmc1Nf881snZltGaWtSFKUNERiYGZNgZuAw4CewFAz65l+jrtf4+593L0PMAqY7u4fRWkrkhQlDZF47AUscfel7r4GuBsYtJHzhwL/bGBbkZxR0hCJRyfg7bT7y1LHvsXMWgGHAg80oO1wM6s2s+oVK1Y0OmiR+ihpiMTD6ji2oUJvRwL/dvePMm3r7uPcvczdyzp0KPgJYFIAYk0aUQfzzKxfahBwcNqxN8zspdQgoaaESKFZBmybdr8zsHwD5w7hf11TmbYVyanYkkbUwbzUeWOAx+v4MQNTA4VlccUpEpNZQHcz62pmLQiJYVLtk8xsc+BA4KFM24okIc4rjaiDeecS+nLfjyWKe+6BP/85lh8tsiHuvhY4h/BhaBFwr7svMLMKM6tIO/UY4Al3/7y+trmLXorKJ5+Ev4EPPpiVHxfn4r66BvP2Tj/BzDoR3jQHAf1qtXfgCTNz4BZ3H1fXLzGz4cBwgO222+7bJzz4IDz9NFRUgNXVVSwSD3efDEyudWxsrfu3A7dHaSsS2fr1MG0a3HZb+Bu4ejWceCL86EeN/tFxXmlEGcy7HrjI3dfVce5+7t6X0L010sz61/VL6h0ILC+H5cth8eKMghcRKThvvgm//S106wYHHwyTJ8MZZ8Ds2XDnnVn5FXFeaUQZzCsD7rZwBdAeONzM1rr7RHdfDuDu75vZBEJ314yMoygvD98rK2HnnTNuLiKS1778EiZODFcVlZXh2MEHw1VXwdFHQ8uWWf11cV5p1DuY5+5d3b2Lu3cB7gfOdveJZtbazNoCmFlr4BBgfoOi6NYNunT535MpIlLo3MPVw8iRsM02oetpyRL4zW/g9dfhiSdgyJCsJwyI8UrD3deaWc1gXlPgtpqBwNTjYzfSvCMwIXUF0gy4y92nNCgQs3C18cADsG4dNG3aoB8jIpK4Dz6Af/wjXFXMmxeSwrHHwrBhMGAANIl/6V2sVW6jDASmHT8t7fZSoHfWAikvh/Hj4YUXoEyzd0WkgHzwATz0UPjg+69/wddfQ79+YUbUkCHQrl1Owymq0ugbdNBB4XtlpZKGiOS/996DCRNCopg2LfSSdO0K550Hp5wCu+2WWGilkTQ6doRddw1J46KLko5GROTb3nknTI+9/36oqgrjFj16hL9ZgwdDnz55sWygNJIGhC6qcePCfOUYBodERDL25pvhauL+++HZZ8OxXXeFSy4JiaJXr7xIFOlKK2nccEP4jxk4MOloRKRULV8Od9wREkXNTot77AFXXBEGtXfaKdn46lE6SePAA8PMqcpKJQ0Ryb2vvoI//AEuvxw+/xz22guuvjokim7dko4ustIpjb7ZZmHGgdZriEiuTZ4cBq9HjQoL7155BWbOhAsuKKiEAaWUNCB0Uc2aBatWJR2JiJSC116Do46CI44IYxNTpoTV2927Jx1Zg5Ve0li3DqZPTzoSESlmX3wBv/pVGMieNi10Q730EvzgB0lH1millTT22SfMnFIXlYjEwR3uuy/Uubv88jADavHi0A3VokXS0WVFaSWNli3hgAOUNEQk+xYsCOMVxx8PW24JM2aEyrLbbJN0ZFlVWkkDQhfV/PlhxaWISGOtXAnnnw+9e4dSRTfdFKbSHnBA0pHFojSTBsDUqcnGISKFbf16uP32sGr7hhvCvhWvvAJnnw3Ninc1Q+kljT32CAW+/vWvpCMRkUI1fz7svz+cfnqYMjtrFtxyC7Rvn3RksSu9pNG0aVjcV1kZBq1ERKJavTqU+NhjD3j1VfjrX+Hf/4Y990w6spwpvaQBoYvqzTdh6dKkIxGRQlFVFYoGXnYZDB0KixbBaaflZA+LfFJa/9oa6VvAiohszMqV8JOfQP/+4UpjyhT4+99LoiuqLqWZNHbaKUyDU9IQkY156CHo2TNUyD7//DCWUQQL9BqjNJNGzRawU6eGGRAiIunefReOOw6OPjpcUTz3HFx3HbRpk3RkiSvNpAEhaXzwQVjaLyICYXLM+PGwyy7w8MOhXHl1dSh2KkCpJw1QF5XExswONbPFZrbEzC7ewDkDzGyumS0ws+lpx98ws5dSj1XnLuoS9uqr4e/CmWfC7rvDiy/CL34BzZsnHVleKd2k0blzGNtQ0pAYmFlT4CbgMKAnMNTMetY6px1wM3CUu/cCjqv1Ywa6ex9318b2cfr6axgzJiSKOXPC+MW0aXm/GVJSSjdpQPhUMX06rFmTdCRSfPYClrj7UndfA9wNDKp1zonAg+7+FoC7v5/jGOXFF8NmSBdfDIcfDgsXwllnldw02kyU9jNTXh520Hr++aQjkeLTCXg77f6y1LF0PYAtzOwpM5ttZqemPebAE6njwzf0S8xsuJlVm1n1ihUrshZ80Vu3Dq65JoxVvPtu2Kf7gQeKrrhgHEo7aQwYEGZSqYtKss/qOFa7BEEzYE/gCOAHwK/MrEfqsf3cvS+he2ukmfWv65e4+zh3L3P3sg4dOmQp9CL31luhGu2FF8KRR4ZptD/6UdJRFYzSThpbbgl9+yppSByWAdum3e8MLK/jnCnu/rm7fwDMAHoDuPvy1Pf3gQmE7i5prH/+M4xdVFfDbbfB/ffDVlslHVVBKe2kAaGL6rnnQjeVSPbMArqbWVczawEMASbVOuch4AAza2ZmrYC9gUVm1trM2gKYWWvgEGB+DmMvPp98AieeGL569QpjGaefHnoaJCNKGuXlYfZEVVXSkUgRcfe1wDnA48Ai4F53X2BmFWZWkTpnETAFmAc8D9zq7vOBjsDTZvZi6vij7j4liX9HUXjqqXB1ce+9oW7U9OmhMq00SPEWfY9q//3DNoyVlXDooUlHI0XE3ScDk2sdG1vr/jXANbWOLSXVTSWN8NVXYZ/u3/8edtwRnnkmzJSSRlHSaNUq7B2ucQ2R4rFgAZx0UuiGqqgIiaN166SjKgqxdk9FWRGbOq+fma0zs8GZts2K8nKYOxc+/DDWXyMiMVu/Puyit+eesHw5TJoEf/6zEkYWxZY0oqyITTtvDKHvN6O2WVNeHmrOTJsW268QkZgtXx66mM87D77//VBX7sgjk46q6MR5pRFlRSzAucADwPsNaJsd/fqF6pXaAlakMD38MOy2W9hFb+zYcIXRsWPSURWlOJNGvStizawTcAzwjcHBKG2zqnnzsNBP4xoihWfOHBg8GLp0gRdegBEjNJU2RnEmjSgrYq8HLnL3dQ1oG07MVhmF8nJYsiSsFhWRwrByZdj3Yuut4fHHoUeP+ttIo8SZNKKsiC0D7jazN4DBwM1mdnTEtkAWyyioVLpIYXGHYcPCB7177inZ7VdzLc6kUe+KWHfv6u5d3L0LcD9wtrtPjNI263bdNXxaUdIQKQx//CM8+CBcdRXsu2/S0ZSM2NZpuPtaM6tZEdsUuK1mRWzq8drjGPW2jStWIPSBHnRQSBru6hMVyWczZ8IFF8BRR8H//V/S0ZSUWBf3RVkRm3b8tPraxq68HO6+GxYtCpvJi0j++egjOP546NQJbr9dH/ByTLWn0mlcQyS/rV8PP/4x/Oc/oZbUFlskHVHJUdJI17Vr+FLSEMlP114LjzwSvvfrl3Q0JUlJo7by8rAyfO3apCMRkXRPPw2jRoU1Geeck3Q0JUtJo7bycli1CmbPTjoSEamxYgUMGRJ6Am69VeMYCVLSqO2gg8J3dVGJ5If16+Hkk+GDD+C++2DzzZOOqKQpadS29dZhwxYlDZH8cOWV8MQTYV1Gnz5JR1PylDTqUl4eCp99+WXSkYiUtmnT4Ne/DntjnHVW0tEIShp1Ky8Pu34980zSkYiUrnffhaFDQz2psWM1jpEnlDTq0r8/NGumLiqRpKxbByeeGCal3Hdf2LpA8oK2e61L27ZhL2ElDZFk/Pa3oWvq9ttDXTjJG7rS2JDycqiuhk8+SToSkdLyxBNw+eVw+ulh9bfkFSWNDTniiDDV7777ko5EpHS8804Y9O7VC268MelopA5KGhuy117hsviWW5KORKQ0rF0bFvCtXg333w+tWiUdkdRBSWNDzKCiIqwMr65OOhqR4jd2bCgVMnYs7LRT0tHIBihpbMzJJ4dPO7rakAYws0PNbLGZLTGzizdwzgAzm2tmC8xseiZti8pHH4X1GOXlYdaU5C0ljY3ZfPMwT/yuu8JexCIRmVlT4CbgMKAnMNTMetY6px1wM3CUu/cCjovatuj85jdh0skf/qD1GHlOSaM+FRXwxRdw551JRyKFZS9gibsvdfc1wN3AoFrnnAg86O5vAbj7+xm0LR4LF8LNN8OIEbDbbklHI/VQ0qhPWRn07Ru6qNyTjkYKRyfg7bT7y1LH0vUAtjCzp8xstpmdmkFbAMxsuJlVm1n1ihUrshR6DrmH7VrbtoVLL006GolASSOKigp46SV49tmkI5HCUVcfS+1PHc2APYEjgB8AvzKzHhHbhoPu49y9zN3LOnTo0Jh4kzF5Mjz+eBjPaN8+6WgkAiWNKIYODZ+ENCAu0S0Dtk273xlYXsc5U9z9c3f/AJgB9I7YtvCtWROuMnbaCUaOTDoaiUhJI4o2bcJMqnvuCbM8ROo3C+huZl3NrAUwBJhU65yHgAPMrJmZtQL2BhZFbFv4brwRXnklDH43b550NBKRkkZUI0aEyrd//3vSkUgBcPe1wDnA44REcK+7LzCzCjOrSJ2zCJgCzAOeB2519/kbapvEvyM2K1aEMYzDDgtfUjDMi2hwt6yszKvjXIi3zz7w8cewaJGmBRYRM5vt7mVJx9FYsb/+s6miAsaPD2OFO++cdDQlLdPXv640MjFiBCxeDDNmJB2JSOGaNw/+8pcwjqGEUXCUNDJx/PHQrl0ocyAimXOH886DLbYIM6ak4ChpZKJVKzj1VHjgAXj//frPF5Fvmjgx7JNx6aUhcUjBUdLI1IgR8PXXYXMYEYnuq6/g5z8P1aOHD086GmkgJY1M9ewJBxwA48aF/TZEJJrrr4elS8MU22baNLRQKWk0REUFvPaatoMVierdd8NufEcdBQcfnHQ00ghKGg1x7LGw1VYaEBeJavTo0D31+98nHYk0UqxJo749AcxskJnNS+0nUG1m+6c99oaZvVTzWJxxZmyTTcL+xQ89BMuLr7qDSFbNmQN//Sv89KfQvXvS0UgjxZY0Iu4JUAn0dvc+wDDg1lqPD3T3Pnm58Gr4cFi3Dm67LelIRPKXe0gW7dvDL3+ZdDSSBXFeadS7J4C7f+b/W5Lemg1U8sxL3buHXcb+8peQPETk2+67L2zhesUVYVMzKXhxJo1IewKY2TFm9jLwKOFqo4YDT6T2Gdjg/LxE9xMYMQLeegumTMnt7xUpBF9+CRdcAL17w7Bh9Z8vBSHOpBFpTwB3n+DuOwNHA5elPbSfu/cldG+NNLP+df2SRPcTGDQIOnZUyXSRulx7bfhQdcMN0LRp0tFIlsSZNDLaE8DdZwA7mFn71P3lqe/vAxMI3V35pUULOOMMePRRePvt+s8XKRXvvAO/+12YaXjggUlHI1kUZ9Kod08AM9vRLJSLNbO+QAvgQzNrbWZtU8dbA4cA82OMteHOOisM9t1aewxfpISNGhXG+q65JulIJMtiSxpR9hMAjgXmm9lcwkyrE1ID4x2Bp83sRcI+A4+6e34OHHTpAoceGpLG2rVJRyOSvJkz4Y47wq58XbsmHY1kWaxr+d19MjC51rGxabfHAGPqaLeUsO1lYRgxAo4+Gh55JHwXKWU33BAWv44alXQkEgOtCM+GI46ATp20QlzEPZTXOfRQaNs26WgkBkoa2dCsGZx5Jjz+eCjIJlKqFi4M2wYcdFDSkUhMlDSy5cwzoUmTsNhPpFTVFPEsL082DomNkka2dO4MP/xhKCuyZk3S0YgkY+pU6NYNtt8+6UgkJkoa2VRRES7NJ05MOhKR3Fu7Fp56SlcZRU5JI5sOOSR8wtKAuJSiF16AlSs1nlHklDSyqWnTUP122jRYvDjpaERya+rU8H3gwGTjkFgpaWTbsGFhNtXNNycdiUhuVVaG/b87dkw6EomRkka2fec7MHRomEWV66q7klcibEI2wMxWpjYam2tml6Q9lr+bkNXlq69CCXR1TRU9JY04jBoFq1fDH/6QdCSSkIibkAFUpTYa6+Pul9Z6LH83IavtuedCKXQNghe9SEnDzH5qZptZMN7M5pjZIXEHV7B22QUGD4Ybb4SPP046Gmmk1J4vm6fdb2dmR9fTrN5NyIrK1KlhnVL/OncwkCIS9UpjmLuvIlSb7QCcDlwVW1TFYPRo+PTTkDik0P3a3VfW3HH3T4Bf19Mm0iZkwD5m9qKZPWZmvdKO5/8mZOkqK6GsDNq1Sy4GyYmoSaNmQ6XDgb+6+4vUvcmS1OjdOyz2u/76kDykkNX1Pqmv2GeUTcjmANu7e2/gT8DEtMfyfxOyGp99FirbajyjJERNGrPN7AlC0ng8tdfF+vjCKhKjR8NHH2ndRuGrNrPrzGwHM+tmZn8AZtfTpt5NyNx9lbt/lro9GWheUJuQ1Xj66bCwT+MZJSFq0jgDuBjo5+5fAM0JXVSyMd/7Hhx8cNj28ssvk45GGu5cYA1wD3Av8CUwsp42UTYh+07aJmR7Ed6PhbUJGYSuqRYtYN99k45EciDqfhr7AHPd/XMzOxnoC9wQX1hFZPTosNhp/Hg455yko5EGcPfPCR+aMmmz1sxqNiFrCtxWswlZ6vGxwGDgJ2a2lpCIhri7m1lHYEIqnzQD7srbTcggDILvsw+0apV0JJIDUa80/gx8YWa9gQuBN4G/xxZVMTnwQNhvPxgzRoUMC5SZPWlm7dLub2Fmj9fXzt0nu3sPd9/B3a9IHRtbsxGZu9/o7r3cvbe7f8/dn0kdX5o61jv1+BUx/dMa76OPQvkQdU2VjKhJY21qG9ZBwA3ufgOgHVaiMINf/hKWLQtbYEohap+aMQWAu38MbJ1cOHnkqafCxksaBC8ZUZPGp2Y2CjgFeDS1cKl5fGEVmR/8APbcE373O+0jXpjWm9l2NXfMrAvfnglVmioroXVr2Ct/x+klu6ImjROArwjrNd4lzDe/Jraoio1ZGNt47TW4556ko5HMjQaeNrM7zOwOYDqgDbAhjGf07w/N9RmyVERKGqlE8Q9gczP7IbDa3TWmkYlBg6BXL7jySliv2cqFJDUIXQYsJsyg+hlh4Lq0LV8OL7+s8YwSE7WMyPHA88BxwPHATDMbHGdgRadJk3C1sXChNmkqMGZ2JlBJSBY/A+4AfpNkTHmhphS6xjNKStTuqdGENRo/dvdTCQuNfhVfWEXq+ONhxx3h8svD4KEUip8C/YA33X0gsAegEsZTp8KWW4bqB1IyoiaNJqmVqTU+zKCt1GjaNFTAfeEFeOyxpKOR6Fa7+2oAM9vE3V8Gdko4pmS5h0HwgQPDVbSUjKj/21PM7HEzO83MTgMeBSbHF1YRO/lk2G47XW0UlmWpdRoTgSfN7CFqlQQpOUuXwltvqWuqBEVaEe7uF5jZscB+hEJs49x9QqyRFasWLeCii2DkyDDHXVtj5j13PyZ18zdmNg3YHMjfFdq5UFkZvmsQvOREvq509wfc/f/c/XwljEYaNizs8Hf55UlHIhly9+nuPim1R0bpmjoVttkGevRIOhLJsY0mDTP71MxW1fH1qZmtylWQRadlS/j5z8Mb79lnk45GJDPu4bVbXh7WIElJ2WjScPe27r5ZHV9t3X2zXAVZlEaMgK22givyt6yQSJ3mz4cVKzSeUaJinfZgZoea2WIzW2Jm36oSamaDzGyemc1N7T62f9S2Ba9NGzj/fHj00TCbSqRQaH1GSYstaaTqU91E2HmsJzDUzHrWOq0S6O3ufYBhwK0ZtC18I0fCZpvpakMKS2VlWG+03Xb1nytFJ84rjb2AJakyz2uAuwlVcv/L3T9LVc8FaM3/isDV27YotGsH554LDz4YVoqL5Lu1a2H6dF1llLA4k0Yn4O20+8tSx77BzI4xs5cJaz+GZdI21X54qmuresWKAlyke955sOmmoQKuSL6bPRtWrdJU2xIWZ9Koa1rFt1azufsEd98ZOBq4LJO2qfbj3L3M3cs6dOjQ0FiT0749VFTAXXeFKrgi+axmPEPri0pWnEljGbBt2v3ObGQVrbvPAHYws/aZti14P/tZKC09ZkzSkYhs3NSpsPvuUIgf0CQr4kwas4DuZtbVzFoAQ4BJ6SeY2Y6W2gjZzPoCLQh1reptW1S22QbOOANuvx3efrve00USsXo1PP20xjNKXGxJw93XAucAjwOLgHvdfYGZVZhZReq0Y4H5ZjaXMFvqBA/qbBtXrHnhwgvDoqlrtLeV5KnnnguJQ+MZJS1S7amGcvfJ1Cps6O5j026PAersk6mrbVHbfns49VS45ZZQZqRPn6QjEvmmyspQqbl//6QjkQSppnE+ueqqMDB+wgnw6adJRyPyTVOnQllZWFskJUtJI5906BBmUS1ZAj/5iUqnS/749FN4/nl1TYmSRt458ED49a/hH/8IA+Mi+aCqKizs0yB4yVPSyEejR4d58CNHaqV4AYtQe22Ama1M1V6ba2aXRG2bc1OnwiabwL77Jh2JJExJIx81bRquNNq0CfuKf/FF0hFJhjKon1bl7n1SX5dm2DZ3KitDwth000TDkOQpaeSr734X7rwTFiwIpUak0DSmflp+1V778EOYO1fjGQIoaeS3Qw6BUaPgL3+Bf/4z6WgkM1Hrp+1jZi+a2WNm1ivDtrmpvTZtWviu8QxBSSP/XXop7LcfDB8Or76adDQSXZT6aXOA7d29N/AnYGIGbcPBXNRemzo1dJWWlcXz86WgKGnku2bNwlVGixZh/cZXXyUdkURTb/00d1/l7p+lbk8Gmudl7bWpU8OsvubNEwtB8oeSRiHYdtsw/faFF+CCC5KORqKJUnvtO2m11/YivB/zq/baO+/A4sXqmpL/UtIoFEceGbaH/dOfYMKEpKORekSsvTaYUHvtReCPwJC8q71WUwpdg+CSYl5Eq47Lysq8uro66TDis2ZNGN9YsiRcdXTpknRERcHMZrt7wXfYx/L6P+20sI/9e+9BE33GLEaZvv71KigkLVrAPffA+vUwZAh8/XXSEUkxcw9XGgMHKmHIf+mVUGi6dYNbb4WZM8PKcZG4LFkS9nfReIakUdIoRMcdFwoaXnMNPPZY0tFIsaoZz1DSkDRKGoXquuvCtpunnhpmuIhk29Sp0LkzdO+edCSSR5Q0ClXLlmF848sv4cQTQwVSkWxxDyvBDzoIrK61hlKqlDQK2c47w5//DDNmwGWXJR2NFJNFi2DFChgwIOlIJM8oaRS6U04J0yIvuwymTEk6GikWVVXh+wEHJBuH5B0ljWJw441hfOO448L6DZHGmjEjVFreYYekI5E8o6RRDFq3hsmTYYst4Igj4K23ko5ICl1VVbjK0HiG1KKkUSy22SZMv/3iCzjsMPj446QjkkL15pthfYa6pqQOShrFpFcvmDgxLMo65hhVxJWG0XiGbISSRrEZMCBUxJ0+PQyQr1+fcEBScGbMgHbtYNddk45E8lCzpAOQGAwdGsY1Lr4YttsOxoxJOiIpJFVVoTBm06ZJRyJ5SEmjWF14YeibvvrqkDhGjkw6IikE778PL78crlJF6qCkUazM4I9/hGXL4P/9v7CR01FHJR2V5Lunnw7f+/dPNg7JWxrTKGY1W8WWlYVS6jNnJh2R5LuqKth0U9hzz6QjkTylpFHsWreGhx8OC7WOPDLMrBLZkKoq2HvvsHeLSB1iTRpmdqiZLTazJWZ2cR2Pn2Rm81Jfz5hZ77TH3jCzl8xsrpkV8XZ8ObD11qHEyPr1YQ3HihVJRyT5aNWqUFFAXVOyEbElDTNrCtwEHAb0BIaaWc9ap70OHOjuuwOXAeNqPT7Q3fsUw1acievePVxxLFsWxja++CLpiCTfPPts+GCh9RmyEXFeaewFLHH3pe6+BrgbGJR+grs/4+41S5efAzrHGI/ssw/cdVcY2zjpJFi3LumIJJ9UVYVptt/7XtKRSB6LM2l0At5Ou78sdWxDzgDSt6Fz4Akzm21mwzfUyMyGm1m1mVWvULdL/Y45Bm64IawcP//8sG+CCISk0bcvtGmTdCSSx+KccltXpbM6/0KZ2UBC0tg/7fB+7r7czLYGnjSzl919xrd+oPs4Ut1aZWVl+gsYxbnnhjUc114L228PP/tZ0hFJ0r76KlyBnnNO0pFInoszaSwDtk273xlYXvskM9sduBU4zN0/rDnu7stT3983swmE7q5vJQ1poKuvDkXpfv5z6NQpTMmV0jVrVkgcGs+QesTZPTUL6G5mXc2sBTAEmJR+gpltBzwInOLur6Qdb21mbWtuA4cA82OMtfQ0aQJ/+1uYKXPSSTB+fNIRSZJqihTuv//Gz5OSF1vScPe1wDnA48Ai4F53X2BmFWZWkTrtEmAr4OZaU2s7Ak+b2YvA88Cj7q5t6bKtZcuwD8f3vw9nnhlqVGmMI2vqm3Kedl4/M1tnZoPTjuV2yvmMGaFK8lZbxf6rpLDFWkbE3ScDk2sdG5t2+0zgzDraLQV61z4uMWjdGiZNCrWGLr441B665ppwJSINljbl/PuErtpZZjbJ3RfWcd4Ywoer2ga6+wexB7tuHTzzDJx4Yuy/Sgqfak9JWP17553Qvj1cd11Y/Dd+PDRvnnRkhey/U84BzKxmyvnCWuedCzwA9MtteGnmzQsL+zSeIRHo46QETZqEqbiXXw533BGm5moBYGPUO+XczDoBxwBj+bbcTTnXpkuSASUN+R8zGD0axo7931iHto1tqChTzq8HLnL3ulZZ7ufufQkVFUaaWZ21Pdx9nLuXuXtZhw4dGhbpjBnQpUuohCxSDyUN+bYRI+Dee6G6OsyueuedpCMqRFGmnJcBd5vZG8BgwoSQo+GbU86Bminn2ecerjR0lSERKWlI3QYPhscegzfeCLu4vfJKvU3kG+qdcu7uXd29i7t3Ae4Hznb3iTmdcv7qq2Hyg5KGRKSkIRt20EHw1FNhbGP//WH27KQjKhgRp5xvSO6mnM9IrZdVZVuJSLOnZOP23DPs5nbIITBgADz0UEgmUq/6ppzXOn5a2u3cTTmvqgql83v0yMmvk8KnKw2pX48eYR5/ly5hP4777086IsmWqqpwFWl1jduLfJuShkSzzTahK6NfPzj+eLjllqQjksZatgxef13jGZIRJQ2Jbost4Ikn4PDDoaICLrtMZUcKWc36DI1nSAaUNCQzrVrBhAlw6qlwySVwwglhNbEUnqoqaNsWeqtij0SnpCGZa94cbr89FDh88MHQZfXSS0lHJZmqqoJ99w279YlEpKQhDWMGF14IlZXhSmPvvUP5ESkMH34I8+era0oypqQhjXPggfDCCyFpnHpqWE2+enXSUUl9/v3v8F2D4JIhJQ1pvO98B558EkaNgnHjQpfH0qVJRyUbU1UVqhv3S664rhQmJQ3JjmbN4Mor4eGHwzTOvn3DPh2Sn2bMCFeHLVsmHYkUGCUNya4f/hDmzIEdd4RBg+Cii2Dt2qSjknSffx7+j9Q1JQ2gpCHZ17Vr6DP/yU/g6quhvBz+85+ko5Iazz0XErmShjSAkobEY5NN4Oabw46A1dWwxx4wbVrSUQmE8YwmTcLYk0iGlDQkXiedBLNmhdXkBx8Mv/sdrF+fdFSlbcYM6NMHNtss6UikAClpSPx69gyJ4/jj4Re/gKOOCusEJPfWrAndU+qakgZS0pDcaNMG7roLbrop1K/q1SusJpfcmjMHvvxSSUMaTElDcscMzj47XHVssw0ceywcdxy8917SkZWOmk2XlDSkgZQ0JPd694aZM8O6jkmTQvfVnXeqYm4uVFXBTjuFjZdEGkBJQ5LRvHlYQT53bvgjdsopYY3HsmVJR1a81q8PU6F1lSGNoKQhydpll/Dp9/rrw37kvXqFUiS66si+BQvg44+VNKRRlDQkeU2bwk9/Gsqrl5WFoofl5apflW014xmqbCuNoKQh+aNbN/jXv8KVRnU17LZbuAJZty7pyIpDVRV07gzbb590JFLAlDQkv5jBWWfBwoUwcCCcf37oTlm0KOnICpt7SBoHHBCeY5EGUtKQ/NS5c6iYe+edsHhxWMF85ZXw9ddJR1aYli6F5cvVNSWNFmvSMLNDzWyxmS0xs4vrePwkM5uX+nrGzHpHbSslwCyUIVm4MFTMHT06jHlMnZp0ZIWnqip81yC4NFJsScPMmgI3AYcBPYGhZtaz1mmvAwe6++7AZcC4DNpKqejYEe69Fx54AFauDIPkRx4JL7+cdGQbFfWDj5n1M7N1ZjY407aRVVXBlluG2WoijRDnlcZewBJ3X+rua4C7gUHpJ7j7M+7+ceruc0DnqG2lBP3oRyFRXHUVTJ8Ou+4K55wDK1YkHdm3RP3gkzpvDPB4pm0zUlUF++8fqtuKNEKcr6BOwNtp95eljm3IGcBjmbY1s+FmVm1m1Svy8I+HZFnLlmFjpyVLYPhwGDs2bPh09dX5tjd51A8+5wIPAO83oG00774Lr76q8QzJijiTRl1TNOpcsWVmAwlJ46JM27r7OHcvc/eyDh06NChQKUBbbx3263jppdBPf9FFoevlnnvyZWFgvR98zKwTcAwwNtO2aT+j/g9NGs+QLIozaSwDtk273xlYXvskM9sduBUY5O4fZtJWhF12gUcegSefDPtDDBkSNhd69tmkI4vywed64CJ3r70QJbsfmqqqoFWrsBGWSCPFmTRmAd3NrKuZtQCGAJPSTzCz7YAHgVPc/ZVM2op8w8EHh7Lf48fDm2+GxHHCCfD660lFFOWDTxlwt5m9AQwGbjazoyO2jW7GjPB8NG/e4B8hUiO2pOHua4FzCAN8i4B73X2BmVWYWUXqtEuArQhvlrlmVr2xtnHFKkWiaVMYNgxeeQUuuSSs89h5Z7jgAvjkk1xHU+8HH3fv6u5d3L0LcD9wtrtPjNI2sk8+gXnz1DUlWRPrVAp3n+zuPdx9B3e/InVsrLuPTd0+0923cPc+qa+yjbUViaRNG/jtb8Pg74knwrXXhsHyKVNyFkLED00ZtW1QIM88E8Z4lDQkS5olHYBIbDp1gr/+NRRDHDUqlGDPIXefDEyudaz2oHfN8dPqa9sgm24Khx8Oe+/d6B8lAkoaUgr69IHHHqv3tKI0cGD4EskSrfQREZHIlDRERCQyJQ0REYlMSUNERCJT0hARkciUNEREJDIlDRERiUxJQ0REIjPPjzLSWWFmK4A3c/gr2wMf5PD31UfxbNyG4tne3Qu+rr5e/4qnHll5/RdV0sg1M6tOr5eVNMWzcfkWT6HLt+dT8WxctuJR95SIiESmpCEiIpEpaTTOuKQDqEXxbFy+xVPo8u35VDwbl5V4NKYhIiKR6UpDREQiU9IQEZHIlDQ2wsy2NbNpZrbIzBaY2U/rOGeAma1M7XE+18wuiTmmN8zspfQ91Ws9bmb2RzNbYmbzzKxvjLHslPbvnmtmq8zsvFrnxP78mNltZva+mc1PO7almT1pZq+mvm+xgbaHmtni1PN1cbZjK3R6D9QbS+LvgZy//t1dXxv4Ar4L9E3dbgu8AvSsdc4A4JEcxvQG0H4jjx8OPAYY8D1gZo7iagq8S1golNPnB+gP9AXmpx27Grg4dftiYMwGYn4N6Aa0AF6s/f9b6l96D2QUVyLvgVy//nWlsRHu/h93n5O6/SmwCOiUbFT1GgT83YPngHZm9t0c/N5y4DV3z+WKZADcfQbwUa3Dg4C/pW7/DTi6jqZ7AUvcfam7rwHuTrWTFL0HMpLIeyDXr38ljYjMrAuwBzCzjof3MbMXzewxM+sVcygOPGFms81seB2PdwLeTru/jNy8yYcA/9zAY7l8fmp0dPf/QPjDB2xdxzlJPVcFSe+BeuXTeyC213+zrIRX5MysDfAAcJ67r6r18BzC5ehnZnY4MBHoHmM4+7n7cjPbGnjSzF5OfdL4b7h1tIl1XrWZtQCOAkbV8XCun59M5Py5KlR6D2xcgb4HGvQ86UqjHmbWnPBm+Ye7P1j7cXdf5e6fpW5PBpqbWfu44nH35anv7wMTCJeY6ZYB26bd7wwsjyuelMOAOe7+Xu0Hcv38pHmvpksi9f39Os5J4rkqOHoPRJJv74HYXv9KGhthZgaMBxa5+3UbOOc7qfMws70Iz+mHMcXT2sza1twGDgHm1zptEnBqagbJ94CVNZepMRrKBi7Lc/n81DIJ+HHq9o+Bh+o4ZxbQ3cy6pj4pDkm1kxS9ByLLt/dAfK//uEb0i+EL2J9wuTYPmJv6OhyoACpS55wDLCDMPHgO2DfGeLqlfs+Lqd85OnU8PR4DbiLMingJKIv5OWpFeANsnnYsp88P4c36H+BrwqenM4CtgErg1dT3LVPnbgNMTmt7OGFG0Gs1z6e+vvHc6j1Qf0yJvgdy/fpXGREREYlM3VMiIhKZkoaIiESmpCEiIpEpaYiISGRKGiIiEpmSRolLVeB8JOk4RJKg13/mlDRERCQyJY0CYWYnm9nzqXr8t5hZUzP7zMyuNbM5ZlZpZh1S5/Yxs+cs7CUwoaaWvpntaGb/ShVOm2NmO6R+fBszu9/MXjazf6StXr3KzBamfs7vE/qni+j1n0+SXnGqr0grPncBHgaap+7fDJxKWKl7UurYJcCNqdvzgANTty8Frk/dngkck7rdkrCSdQCwklB3pgnwLGEV8JbAYv63j3y7pJ8HfZXml17/+fWlK43CUA7sCcwys7mp+92A9cA9qXPuBPY3s80JL/DpqeN/A/qn6vV0cvcJAO6+2t2/SJ3zvLsvc/f1hDIRXYBVwGrgVjP7EVBzrkiu6fWfR5Q0CoMBf3P3Pqmvndz9N3Wct7GaMHWVQa7xVdrtdUAzd19LqB76AGEDlymZhSySNXr95xEljcJQCQxO7R9Qs//v9oT/v8Gpc04Ennb3lcDHZnZA6vgpwHQPeyAsM7OjUz9jEzNrtaFfaGH/hM09lHI+D+iT9X+VSDR6/ecRbcJUANx9oZn9krBbWRNCNcuRwOdALzObTeiXPSHV5MfA2NSbYilweur4KcAtZnZp6mcct5Ff2xZ4yMxaEj6lnZ/lf5ZIJHr95xdVuS1gZvaZu7dJOg6RJOj1nwx1T4mISGS60hARkch0pSEiIpEpaYiISGRKGiIiEpmShoiIRKakISIikf1/QfPHMlE40t4AAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt \n",
    "x = list(range(1, len(hits_for_plot)+1))\n",
    "plt.subplots_adjust(left=None,bottom=None,right=None,top=None,wspace=0.35,hspace=0.15)\n",
    "plt.subplot(1,2,1)\n",
    "plt.xlabel('epochs')\n",
    "plt.ylabel('loss')\n",
    "plt.plot(x, loss_for_plot, 'r') \n",
    "plt.subplot(1,2,2)\n",
    "plt.xlabel('epochs')\n",
    "plt.ylabel('acc')\n",
    "plt.plot(x, hits_for_plot, 'r') \n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "b143e870",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('./datasets/test_dataset.csv')\n",
    "user_for_test = df['user_id'].tolist()   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b314cf2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "predict_item_id = []\n",
    "def chunks(l, n):\n",
    "    for i in range(0, len(l), n):\n",
    "        yield l[i:i+n]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "c30464a4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████████████████████████████████████████████████████| 53424/53424 [09:14<00:00, 96.41it/s]\n"
     ]
    }
   ],
   "source": [
    "f = open('./datasets/submission.csv', 'w', encoding='utf-8')\n",
    "for user in tqdm.tqdm(user_for_test):\n",
    "    # 将用户已经交互过的物品排除\n",
    "    user_visited_items = traindataset.user_book_map[user]\n",
    "    items_for_predict = list(set(range(traindataset.book_nums)) - set(user_visited_items)) \n",
    "    results = []\n",
    "    user = torch.LongTensor([user]).to(device)\n",
    "    for batch in chunks(items_for_predict, 1024):\n",
    "        batch = torch.LongTensor(batch).unsqueeze(0).to(device)\n",
    "        result = model.predict(user, batch).view(-1).detach().cpu()\n",
    "        results.append(result) \n",
    "    results = torch.cat(results, dim=-1)\n",
    "    predict_item_id = (-results).argsort()[:10]\n",
    "    list(map(lambda x: f.write('{},{}\\n'.format(user.cpu().item(), x)), predict_item_id))\n",
    "f.flush()\n",
    "f.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a7768e4",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
